﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using COUP.Core;
using COUP.Core.Enums;
using COUP.Core.Entities;
using COUP.Core.Networking;

namespace COUP.Game.Networking
{
    public static class PacketHandler_4267
    {
        public static void Handle_Packet(ClientState Owner, Packet Data)
        {
            try
            {
                switch (Data.Type)
                {
                    case 1001:
                        NewCharacter(Owner, Data); break;
                    case 1004:
                        Message(Owner, Data); break;
                    case 1005:
                        EntityMove(Owner, Data); break;
                    case 1009:
                        ItemType(Owner, Data); break;
                    case 1010:
                        GeneralData(Owner, Data); break;
                    case 1052:
                        LoginRequest(Owner, Data); break;
                    default:
                        Program.Output("Received Unknown Packet: Length " + Data.Length + ", Type " + Data.Type); break;
                }
            }
            catch (Exception e)
            {
                Program.Output(e.ToString());
            }
        }

        internal static void Message(ClientState Owner, Packet Data)
        {
            ChatPacket chatData = new ChatPacket(Data);

            if (chatData.Message.StartsWith("!"))
            {
                UseCommand(Owner, chatData); return;                
            }

            switch (chatData.MessageType)
            {
                case ChatType.Talk:
                    {
                        Owner.SendToLocal(Data); break;
                    }
                case ChatType.Whisper:
                    {
                        Character Target = Program.Maps[Owner.Entity.Map].GetEntity(chatData.To) as Character;

                        if (Target == null)
                        {
                            //Send Character is not online
                            return;
                        }

                        Target.Parent.Send(new Packet(Data.Buffer));
                        break;
                    }
            }
        }

        internal static void EntityMove(ClientState Owner, Packet Data)
        {
            MovementPacket movementData = new MovementPacket(Data);

            List<iEntity> PreviousEntities = Owner.Entity.GetLocalEntities;
            //Make alterations to the clients position
            Owner.Entity.X += Settings.MoveX[(byte)movementData.Direction]; Owner.Entity.Y += Settings.MoveY[(byte)movementData.Direction];
            Owner.Entity.InformVisibleArea();

            List<iEntity> CurrentEntities = Owner.Entity.GetLocalEntities;

            if (!Program.Maps[Owner.Entity.Map].CheckMove(Owner.Entity.Location))
            {
                Owner.Entity.Location = Owner.Entity.PreviousLocation;
                Owner.Infractions++;
                Owner.Disconnect();
                return;
            }            

            foreach (iEntity Entity in CurrentEntities)
            {
                if (Entity.UniqueID == Owner.Entity.UniqueID)
                    continue;

                if (Entity is Character)
                {
                    Character Target = Entity as Character;
                    if (!PreviousEntities.Contains(Entity))
                    {
                        Target.Parent.Send(new SpawnPacket(Owner.Entity));
                        Owner.Send(new SpawnPacket(Target));

                        Program.Output("Spawning Entity: " + Entity.Name + " UniqueID: " + Entity.UniqueID);
                    }
                }
                else if (Entity is Monster)
                {
                    Monster Target = Entity as Monster;
                    if (!PreviousEntities.Contains(Entity))
                    {
                        Owner.Send(new SpawnPacket(Target));

                        Program.Output("Spawning Entity: " + Entity.Name + " UniqueID: " + Entity.UniqueID);
                    }
                }
            }

            //Packet doesnt need altering so just resend the packet
            Owner.SendToLocal(Data, true);
        }

        internal static void NewCharacter(ClientState Owner, Packet Data)
        {
            Program.Database.Submit(SubmitType.NewCharacter, new NewCharacterPacket(Data), Program.ServerID);
            //Informs the client that the character was created successfully
            Owner.Send(new ChatPacket("ALL_USERS", "SYSTEM", "ANSWER_OK", 0, ChatType.Dialog));
            //Disconnect the client, in 4267 you cannot directly proceed to login.
            Owner.Disconnect();
        }

        internal static void ItemType(ClientState Owner, Packet Data)
        {
            ItemDataPacket itemData = new ItemDataPacket(Data);

            switch (itemData.SubType)
            {
                case ItemDataType.DepositCash:
                    {
                        if (Owner.Entity.InventoryGold < itemData.Money)
                        {
                            //Client tried to deposit more money than they had, nothing wrong with this so just ignore the deposit
                            return;
                        }

                        if (Owner.Entity.BankGold + itemData.Money > 1000000000)
                        {
                            //Client tried to deposit more than the maximum amount of gold, nothing wrong with this so just ignore the deposit
                            return;
                        }

                        Owner.Entity.InventoryGold -= itemData.Money;
                        Owner.Entity.BankGold += itemData.Money;
                        break;
                    }
                case ItemDataType.EquipItem:
                    {
                        if (itemData.Position == 0) 
                        { 
                            //Client has attempted to use an item which cannot be equiped, we'll move to a seperate method to keep things tidy
                            UseConsumable(Owner, itemData);
                            return;
                        }

                        if (!Calculations.CheckUseable(Owner.Entity, Program.ItemData[0]))
                        {
                            //Client tried to equip an item that they cannot use, nothing wrong with this so we'll just ignore the equip request
                            return;
                        }

                        Item workingItem = Owner.Entity.Inventory.GetItem((int)itemData.UniqueID);

                        Owner.Entity.Equipment.Equip(workingItem, Owner.Entity.Inventory);

                        break;
                    }
                case ItemDataType.WithdrawCash:
                    {
                        if (Owner.Entity.BankGold < itemData.Money)
                        {
                            //Client tried to withdraw more money than they had, nothing wrong with this so just ignore the withdraw
                            return;
                        }

                        if (Owner.Entity.InventoryGold + itemData.Money > 1000000000)
                        {
                            //Client tried to withdraw more than the maximum amount of gold, nothing wrong with this so just ignore the withdraw
                            return;
                        }

                        Owner.Entity.InventoryGold += itemData.Money;
                        Owner.Entity.BankGold -= itemData.Money;
                        break;
                    }
                case ItemDataType.Ping:
                    {
                        //Send the ping packet back, no requirement to modify this packet
                        Owner.Send(Data);
                        break;
                    }
                default:
                    Program.Output("Received Unhandled ItemType Type: " + itemData.SubType); break;
            }
        }

        internal static void GeneralData(ClientState Owner, Packet Data)
        {
            GeneralDataPacket generalData = new GeneralDataPacket(Data);

            switch (generalData.SubType)
            {
                case GeneralDataType.SetLocation:
                    {
                        //Send client's current position
                        if (Owner.LoginState == LoginProcess.LoggingIn)
                            Owner.Send(new ChatPacket("", "SYSTEM", "Welcome " + Owner.Entity.Name + "! Please ensure you have the latest core revision!", Owner.Entity.UniqueID, ChatType.Service));

                        Program.Maps[Owner.Entity.Map].Insert(Owner.Entity);

                        Owner.Send(new GeneralDataPacket(Owner.Entity.UniqueID, Owner.Entity.X, Owner.Entity.Y, 0, Owner.Entity.Map, GeneralDataType.SetLocation));
                        Owner.Send(new GeneralDataPacket(Owner.Entity.UniqueID, Owner.Entity.X, Owner.Entity.Y, 65535, 65535, GeneralDataType.ConfirmMap));

                        foreach (iEntity Entity in Owner.Entity.GetLocalEntities)
                        {
                            if (Entity.UniqueID == Owner.Entity.UniqueID)
                                continue;

                            if (Entity is Character)
                            {
                                Character Target = Entity as Character;
                                Target.Parent.Send(new SpawnPacket(Owner.Entity));
                                Owner.Send(new SpawnPacket(Target));
                            }
                            else if (Entity is Monster)
                            {
                                Monster Target = Entity as Monster;
                                Owner.Send(new SpawnPacket(Target));
                            }

                            Program.Output("Spawning Entity: " + Entity.Name + " UniqueID: " + Entity.UniqueID);
                        }
                        break;
                    }
                case GeneralDataType.ConfirmItems:
                    {
                        //Send client's items and equipment
                        if (Owner.LoginState == LoginProcess.LoggingIn)
                            Owner.Send(new GeneralDataPacket(Owner.Entity.UniqueID, 0, 0, 0, 0, GeneralDataType.ConfirmItems));
                        break;
                    }
                case GeneralDataType.ConfirmFriends:
                    {
                        //Send client's friend and enemy info
                        Owner.Send(new GeneralDataPacket(Owner.Entity.UniqueID, 0, 0, 0, 0, GeneralDataType.ConfirmFriends));
                        break;
                    }
                case GeneralDataType.ChangePKMode:
                    {
                        //Send client's pkmode
                        Owner.Entity.PkMode = generalData.PkMode;
                        Owner.Send(new GeneralDataPacket(Owner.Entity.UniqueID, 0, 0, 0, (int)Owner.Entity.PkMode, GeneralDataType.ChangePKMode));
                        break;
                    }
                case GeneralDataType.CompleteLogin:
                    {
                        //Respond to the client's request to complete login
                        Owner.LoginState = LoginProcess.LoginComplete;
                        Owner.Send(new GeneralDataPacket(Owner.Entity.UniqueID, 0, 0, 0, 0, GeneralDataType.CompleteLogin));
                        Program.Output("Character '" + Owner.Entity.Name + "' Completed Login");
                        break;
                    }
                case GeneralDataType.ConfirmSpells:
                    {
                        //Send client's spells
                        Owner.Send(new GeneralDataPacket(Owner.Entity.UniqueID, 0, 0, 0, 0, GeneralDataType.ConfirmSpells));
                        break;
                    }
                case GeneralDataType.ConfirmGuild:
                    {
                        //Send client's guild info
                        Owner.Send(new GeneralDataPacket(Owner.Entity.UniqueID, 0, 0, 0, 0, GeneralDataType.ConfirmGuild));
                        break;
                    }
                case GeneralDataType.GetSurroundings:
                    {
                        //Send nearby clients, mobs, items and npcs
                        foreach (iEntity Entity in Owner.Entity.GetLocalEntities)
                        {
                            Program.Output("Spawning Entity: " + Entity.Name + " UniqueID: " + Entity.UniqueID);
                        }
                        break;
                    }
                case GeneralDataType.ChangeAction:
                    {
                        Owner.Entity.Action = generalData.Action;
                        Owner.SendToLocal(generalData);
                        break;
                    }
                case GeneralDataType.ChangeAngle:
                    {
                        Owner.Entity.Angle = generalData.Angle;
                        Owner.SendToLocal(generalData);
                        break;
                    }
                case GeneralDataType.SpawnEntity:
                    {
                        iEntity Target = Program.Maps[Owner.Entity.Map].GetEntity(generalData.UniqueID);

                        if (Target.UniqueID == Owner.Entity.UniqueID)
                            break;

                        if(Target is Character)
                            Owner.Send(new SpawnPacket(Target as Character));
                        else if (Target is Monster)
                            Owner.Send(new SpawnPacket(Target as Monster));

                        break;
                    }
                case GeneralDataType.EnterPortal:
                    {
                        Owner.SendToLocal(new GeneralDataPacket(Owner.Entity.UniqueID, 0, 0, 0, 0, GeneralDataType.RemoveEntity));

                        //Database lookup to retrieve exit cordinates.
                        //The stored procedure will find the portal closest to the players current cordinates and if within 3 cords of the center
                        //will return valid exit cordinates, if not the player needs to get closer and the procedure will return nothing.
                        int[] PortalData = (int[])Owner.Database.Select(SelectType.GetPortal, Owner.Entity.Map, Owner.Entity.Location);

                        if (PortalData[0] == 0)
                        {
                            //Player wasnt close enough to a portal, they could be trying to exploit, possible infraction.
                            return;
                        }

                        //Set new cordinates and inform the quadtree of the movement.
                        Owner.Entity.Map = PortalData[0]; Owner.Entity.X = PortalData[1]; Owner.Entity.Y = PortalData[2];
                        Owner.Entity.InformVisibleArea();

                        Owner.SendToLocal(new GeneralDataPacket(Owner.Entity.UniqueID, Owner.Entity.X, Owner.Entity.Y, 0, Owner.Entity.Map, GeneralDataType.SetLocation), true);
                        break;
                    }
                case GeneralDataType.Jump:
                    {
                        if (Calculations.Distance(Owner.Entity.Location, generalData.X, generalData.Y) >= 16)
                        {
                            Owner.Disconnect();
                        }

                        List<iEntity> PreviousEntities = Owner.Entity.GetLocalEntities;

                        Owner.Entity.X = generalData.X; Owner.Entity.Y = generalData.Y;
                        Owner.Entity.InformVisibleArea();

                        List<iEntity> CurrentEntities = Owner.Entity.GetLocalEntities;

                        if (!Program.Maps[Owner.Entity.Map].CheckMove(Owner.Entity.Location))
                        {
                            Owner.Entity.Location = Owner.Entity.PreviousLocation;
                            Owner.Infractions++;
                            Owner.Disconnect();
                            return;
                        }                        

                        foreach (iEntity Entity in CurrentEntities)
                        {
                            if (Entity.UniqueID == Owner.Entity.UniqueID)
                                continue;

                            if (Entity is Character)
                            { 
                                if (!PreviousEntities.Contains(Entity))
                                {
                                    Character Target = Entity as Character;
                                    Target.Parent.Send(new SpawnPacket(Owner.Entity));
                                    Owner.Send(new SpawnPacket(Target));

                                    Program.Output("Spawning Entity: " + Entity.Name + " UniqueID: " + Entity.UniqueID);
                                }
                            }
                            else if (Entity is Monster)
                            {                                
                                if (!PreviousEntities.Contains(Entity))
                                {
                                    Monster Target = Entity as Monster;
                                    Owner.Send(new SpawnPacket(Target));

                                    Program.Output("Spawning Entity: " + Entity.Name + " UniqueID: " + Entity.UniqueID);
                                }
                            }
                        }

                        Owner.SendToLocal(Data, true);
                        break;
                    }
                default:
                    Program.Output("Received Unhandled GeneralData Type: " + generalData.SubType); break;
            }
        }

        internal static void LoginRequest(ClientState Owner, Packet Data)
        {
            //Populate the LoginRequest packet with data so that we can utilize accessors
            LoginReqPacket loginReq = new LoginReqPacket(Data);
            //Setup the cryptographer so that its not just using the default keys
            Owner.Cryptographer.GenerateKeys(loginReq.AccountID, loginReq.Token);
            //Reconstruct the accountID from the data in the packet, this basic hashing of the accountid is
            //designed to stop people requesting to login to other peoples characters, it is not secure currently.
            int AccountID = loginReq.AccountID;
            AccountID ^= loginReq.Token;
            AccountID ^= Owner.RemoteHash;
            Owner.AccountID = AccountID;
            //Determine if the account needs to create a char for this server
            //Just like in auth we will have a database lookup for this            
            LoginError loginError = LoginError.None;

            switch (loginError)
            {
                case LoginError.None:
                    {
                        //Creates the character object within the state object
                        Owner.Entity = new Character(Owner, AccountID);
                        //Informs the client that it has the go ahead to begin the login process
                        Owner.Send(new ChatPacket("ALL_USERS", "SYSTEM", "ANSWER_OK", Owner.Entity.UniqueID, ChatType.LoginInformation));
                        //Sends the character information packet to the client
                        Owner.Send(new CharacterPacket(Owner.Entity));
                        break;
                    }
                case LoginError.Character:
                    {
                        //Informs the client to create a new character
                        Owner.Send(new ChatPacket("ALL_USERS", "SYSTEM", "NEW_ROLE", 0, ChatType.LoginInformation));
                        break;
                    }
            }
        }

        internal static void UseConsumable(ClientState Owner, ItemDataPacket Data)
        {

        }

        internal static void UseCommand(ClientState owner, ChatPacket Data)
        {
            switch (Data.Message)
            {

            }
        }
    }
}
